package cn.qianxun.meta.auth.application.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.qianxun.meta.auth.application.dto.AddAuthApplicationDTO;
import cn.qianxun.meta.auth.application.dto.AuthApplicationQueryDTO;
import cn.qianxun.meta.auth.application.enums.ApplicationTypeEnum;
import cn.qianxun.meta.auth.application.service.IAuthApplicationService;
import cn.qianxun.meta.auth.application.vo.AddAuthApplicationVO;
import cn.qianxun.meta.auth.application.vo.ApplicationNameListVO;
import cn.qianxun.meta.auth.application.vo.AuthApplicationVO;
import cn.qianxun.meta.common.core.dto.ChangeStatusDTO;
import cn.qianxun.meta.common.core.dto.RowsData;
import cn.qianxun.meta.common.core.enums.DeviceType;
import cn.qianxun.meta.common.core.field.AbstractFieldAssert;
import cn.qianxun.meta.common.core.utils.DateUtils;
import cn.qianxun.meta.common.core.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import cn.qianxun.meta.auth.application.entity.AuthApplication;
import cn.qianxun.meta.auth.application.mapper.AuthApplicationMapper;
import cn.qianxun.meta.common.core.constant.Constants;
import cn.qianxun.meta.common.core.exception.ServiceException;
import cn.qianxun.meta.common.core.utils.code.CodeRandomUtil;
import cn.qianxun.meta.common.core.utils.sign.RsaUtils;
import cn.qianxun.meta.common.core.utils.uuid.IdUtils;
import cn.qianxun.meta.common.satoken.utils.LoginHelper;
import cn.qianxun.meta.common.satoken.utils.StpAppIdUtil;
import cn.qianxun.meta.user.api.vo.ApiAuthApplicationVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 认证中心应用表 服务实现类
 * </p>
 *
 * @author fuzhilin
 * @since 2023-09-12 10:12:30
 */
@Slf4j
@Service
public class AuthApplicationServiceImpl extends ServiceImpl<AuthApplicationMapper, AuthApplication> implements IAuthApplicationService {

    @Override
    public RowsData<AuthApplicationVO> selectApplicationList(AuthApplicationQueryDTO dto) {
        LambdaQueryWrapper<AuthApplication> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotNull(dto.getAppId())) {
            wrapper.like(AuthApplication::getAppId, dto.getAppId());
        }
        if (StringUtils.isNotEmpty(dto.getName())) {
            wrapper.like(AuthApplication::getName, dto.getName());
        }
        if (StringUtils.isNotEmpty(dto.getSubject())) {
            wrapper.like(AuthApplication::getSubject, dto.getSubject());
        }
        try (Page<Object> page = PageHelper.startPage(dto.getPageNum(), dto.getPageSize())) {
            List<AuthApplication> applicationList = baseMapper.selectList(wrapper);
            List<AuthApplicationVO> list = applicationList.stream().map(authApplication -> {
                AuthApplicationVO applicationVO = new AuthApplicationVO();
                BeanUtils.copyProperties(authApplication, applicationVO);
                applicationVO.setOnlineStatus(StpAppIdUtil.isLogin(DeviceType.APPID.getDevice() + ":" + authApplication.getAppId()));
                return applicationVO;
            }).collect(Collectors.toList());
            return new RowsData<AuthApplicationVO>(list, page.getTotal(), dto.getPageNum());
        }
    }

    @Override
    public AddAuthApplicationVO addApplication(AddAuthApplicationDTO dto) {
        AuthApplication application = new AuthApplication();
        BeanUtils.copyProperties(dto, application);
        application.setAppId(Long.valueOf(CodeRandomUtil.createAppId(true, 16)));
        String appSecret = IdUtils.simpleUUID() + IdUtils.simpleUUID();
        application.setAppSecret(BCrypt.hashpw(appSecret));
        application.setCreateBy(LoginHelper.getUserName());
        if (StringUtils.isNotEmpty(dto.getAccessTimeLimit())) {
            application.setAccessTimeLimit(DateUtils.parseDate(dto.getAccessTimeLimit()));
        }
        Map<String, String> keyPair = RsaUtils.generateKeyPair();
        if (CollectionUtils.isEmpty(keyPair)) {
            throw new ServiceException("未获取到公私钥信息");
        }
        application.setPubKey(keyPair.get("RSAPublicKey"));
        application.setPrivateKey(keyPair.get("RSAPrivateKey"));
        baseMapper.insert(application);
        AddAuthApplicationVO applicationVO = new AddAuthApplicationVO();
        applicationVO.setAppSecret(appSecret);
        applicationVO.setPrivateKey(application.getPrivateKey());
        return applicationVO;
    }

    @Override
    public void editApplication(AddAuthApplicationDTO dto) {
        AbstractFieldAssert.isNull(dto.getId(), "应用id不能为空");
        AuthApplication application = baseMapper.selectById(dto.getId());
        if (StringUtils.isNull(application)) {
            throw new ServiceException("所修改数据不存在");
        }
        BeanUtils.copyProperties(dto, application);
        application.setUpdateBy(LoginHelper.getUserName());
        if (StringUtils.isNotEmpty(dto.getAccessTimeLimit())) {
            application.setAccessTimeLimit(DateUtils.parseDate(dto.getAccessTimeLimit()));
        }
        baseMapper.updateById(application);
    }

    @Override
    public AuthApplicationVO details(Long id) {
        AbstractFieldAssert.isNull(id, "应用id不能为空");
        AuthApplication application = baseMapper.selectById(id);
        AuthApplicationVO vo = new AuthApplicationVO();
        BeanUtils.copyProperties(application, vo);
        vo.setOnlineStatus(StpAppIdUtil.isLogin(DeviceType.APPID.getDevice() + ":" + application.getAppId()));
        return vo;
    }

    @Override
    public List<ApplicationNameListVO> applicationNameList(Integer appType) {
        AbstractFieldAssert.isNull(appType, "应用类型不能为空");
        List<AuthApplication> list = baseMapper.selectList(new LambdaQueryWrapper<AuthApplication>().eq(AuthApplication::getAppType, appType));
        return list.stream().map(authApplication -> {
            ApplicationNameListVO vo = new ApplicationNameListVO();
            BeanUtils.copyProperties(authApplication, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public void changeStatus(ChangeStatusDTO dto) {
        AuthApplication application = baseMapper.selectById(dto.getBusinessId());
        if (StringUtils.isNull(application)) {
            throw new ServiceException("未获取到应用信息");
        }
        application.setAppStatus(Integer.valueOf(dto.getStatus()));
        baseMapper.updateById(application);
    }

    @Override
    public ApiAuthApplicationVO getAppInfoByAppId(String appId, String appSecret) {
        AbstractFieldAssert.isBlank(appId, "appId不能为空");
        AbstractFieldAssert.isBlank(appSecret, "appSecret不能为空");
        //API
        List<AuthApplication> applicationList = baseMapper.selectList(new LambdaQueryWrapper<AuthApplication>().eq(AuthApplication::getAppId, appId).eq(AuthApplication::getAppType, Constants.ONE));
        if (CollectionUtils.isEmpty(applicationList)) {
            throw new ServiceException("登录应用【" + appId + "】不存在");
        }
        if (applicationList.size() > 1) {
            throw new ServiceException("登录应用【" + appId + "】信息异常，请联系管理员");
        }
        AuthApplication application = applicationList.get(0);
        if (!Integer.valueOf(Constants.ONE).equals(application.getAppStatus())) {
            throw new ServiceException("该应用【" + appId + "】已停用，请联系管理员");
        }
        if (!BCrypt.checkpw(appSecret, application.getAppSecret())) {
            throw new ServiceException("appSecret不正确");
        }
        ApiAuthApplicationVO applicationVO = new ApiAuthApplicationVO();
        BeanUtils.copyProperties(application, applicationVO);
        //获取菜单权限
        applicationVO.setMenuPermission(getAppApiPermissionByAppId(appId));
        return applicationVO;
    }

    @Override
    public String resetSecret(Long id) {
        AuthApplication application = baseMapper.selectById(id);
        if (StringUtils.isNull(application)) {
            throw new ServiceException("未获取到应用信息");
        }
        String appSecret = IdUtils.simpleUUID() + IdUtils.simpleUUID();
        application.setAppSecret(BCrypt.hashpw(appSecret));
        baseMapper.updateById(application);
        //将旧的密钥登录信息移除
        StpAppIdUtil.logout(DeviceType.APPID.getDevice() + ":" + application.getAppId());
        return appSecret;
    }

    @Override
    public void forceOffline(Long id) {
        AuthApplication application = baseMapper.selectById(id);
        if (StringUtils.isNull(application)) {
            throw new ServiceException("未获取到应用信息");
        }
        if (ApplicationTypeEnum.API.getCode().equals(application.getAppType())) {
            boolean login = StpAppIdUtil.isLogin(DeviceType.APPID.getDevice() + ":" + application.getAppId());
            if (!login) {
                throw new ServiceException("该应用尚未在线，无需强制下线");
            }
        } else {
            //TODO 后续统一认证业务
            log.info("后续统一认证业务");
        }

        StpAppIdUtil.logout(DeviceType.APPID.getDevice() + ":" + application.getAppId());
    }

    @Override
    public void updateApplicationLoginNum(Long id) {
        AuthApplication application = baseMapper.selectById(id);
        if (StringUtils.isNotNull(application)) {
            Integer alreadyLoginNum = application.getAlreadyLoginNum();
            if (StringUtils.isNotNull(alreadyLoginNum) && -1 != alreadyLoginNum) {
                application.setAlreadyLoginNum(alreadyLoginNum + 1);
                baseMapper.updateById(application);
            }
        }
    }

    /**
     * 根据appId获取应用权限
     *
     * @param appId appId
     * @return Set<String>
     */
    public Set<String> getAppApiPermissionByAppId(String appId) {
        List<String> permissionList = baseMapper.selectAppApiPermissionByAppId(appId);
        return new HashSet<String>(permissionList);
    }
}
